openflfandomcom-20200213-history
Sprites
Sprites involve the rendering of 2-dimensional graphics and on this page, you will be able to learn everything that you can do with Sprites, such as working with the Graphics class, transformations, and containing other DisplayObject's. There will also be some tips on how to best use Sprites and keep them optimal for your games performance. Creating a Sprite The Sprite class is within the display ''package of openfl, thus you will be importing ''openfl.display.Sprite ''in order to use it. The Sprite can be extended, as seen briefly in the Main entry-point discussion, and also instantiated. To create a Sprite and add it onto the display list, there must be a reference to the stage. If the stage reference is ''null ''when you attempt to add any DisplayObject onto the stage, all rendering will fail. Since 3.x, this is no longer an issue as the ADDED_TO_STAGE event is internally listened and thus allows us to omit it manually. However, local stage references (such as that from the extended Sprite) will still be ''null, so if you want to access the local stage reference, you will have to listen on the event manually yourself. Firstly, we need to import the class: import openfl.display.Sprite; Allowing us to use the class within our document. Haxe uses class importingHaxe Import Behaviour, unlike languages such as C# which uses namespace importing, and similarly C and C++ only require the import of a header file, containing structs, objects and function metadata. The more precise class import approach allows the compiler to only include those classes within the scope of that document, speeding up the compiling process. If you import a class that you are not using, the compiler will be adding a class that is only taking up space on someone elses' computer when you ship a game. They may not know that, but it's wasted space nonetheless. Tip For best performance, only import classes you use. To extend from this Sprite, we can create and define our own class. class Main extends Sprite This means we can access all of the members of Sprite within our defined class called Main. Or we can instantiate it instead: private var _mySprite:Sprite = new Sprite(); In the above line, we have declared and set the value of _mySprite with an instantiation of Sprite. ''Instantiation is computer jargon for "creating a new instance of a reference or value type." Classes are reference types, which means that they get created on the heap and are often garbage-collected in GC languages.Structs vs Classes - MSDN Flash has its own Garbage collectorUnderstanding Garbage collection in Flash Player 9 and above, which is a tool that automatically handles the memory of our application, so that we do not have to dispose of or set anything to null once we have used it. C++, on the other, does not have a garbage collector by default (at least native C++), but Neko does.Neko Garbage Collector Garbage collection in video games can slow down performance, but for the purposes of this tutorial, let's assume we will be targeting C++ so we don't have to worry about it too much. Haxe requires all declared variables to be set before it can be used. private var _mySprite:Sprite; The above line means we have declared the variable ''_mySprite ''but have not set it. private var _mySprite = new Sprite(); The above line means we have set the variable ''_mySprite ''but have not declared it. The compiler in this instance must do one extra check to determine what type of ''_mySprite ''variable is, so it is often good practice to declare your variables before setting them. You can get away with leaving out declaration if the type is less than 16 bytes in size and only live for short periods of time, such as local variables in the scope of a function. It is also fine to leave out declaration for a ''Dynamic ''as these types are resolved at runtime, instead of compile-time. However, the use of ''Dynamic ''types can be slow in large sizes. For a look at what our final code would look like, it may be similar to this: package; import openfl.display.Sprite; class Main extends Sprite { private var _mySprite:Sprite; public function new() { super(); _mySprite = new Sprite(); addChild(_mySprite); } } Understanding Sprites and Bitmaps Sprite's extend from the ''DisplayObjectContainer ''class which contains information to add and remove children to its unique list hierarchy. Similarly, the Stage also extends from it, and is the top-most object in the global display list hierarchy. If you add objects to the stage, you are adding to the top-most object. Sprites have some additional functionality in that it can be interactive, while Bitmap's - which only extend from DisplayObject's - are simply objects on their own. Because Bitmap's do not extend the InteractiveObject class, Mouse and Keyboard events do not apply to them. ''Bitmap ''classes are cheaper to use because they directly inherit from DisplayObject and consequently do not take up as much memory. That means if you ever want to have more than one of the same image, but need not have the interactivity, Bitmap's can save memory and provide performance benefits. Bitmap's also hold the ''bitmapData ''property, which allow you to attach loaded image assets. When this property changes, the rendered image also changes to make that of the loaded asset, like so: private var _myBitmap:Bitmap = new Bitmap(); _myBitmap.bitmapData = Assets.getBitmapData("img/myImg.png"); Sprites have no such functionality, but can hold multiple Bitmap's, because it has its own display list hierarchy. Sprites are particularly useful for managing scenes and reusable game objects (by game object's, I mean objects that have common drawing routines for any particular task). Let's add our Bitmap to a Sprite. addChild(_myBitmap); And now let's take a look at our final code: package; import openfl.display.Sprite; import openfl.display.Bitmap; class Main extends Sprite { private var _myBitmap:Bitmap = new Bitmap(); public function new() { super(); _myBitmap.bitmapData = Assets.getBitmapData("img/myImg.png"); addChild(_myBitmap); } } If you have an image called "myImg.png" in the img folder of your assets, then the result is that the Image is located in the top-left corner of the game window, or the position of (0, 0). Transformations Transformations are concerned with size, location and other mathematical operations of a DisplayObject. Location is referred to as the ''x ''and ''y ''properties of a DisplayObject, while ''width ''and ''height ''refer to the size of a DisplayObject. You can set these properties and the results will apply on-screen. _mySprite.x = 50; _mySprite.y = 50; Because setting the ''witdth ''and ''height ''properties of a Sprite can cause rendering issues, as well as Bitmap's, it may be best to use the ''scaleX ''and ''scaleY ''properties to overcome this issue. These take a Float as a percentage of the base dimensions of the DisplayObject. The scale properties scale the objects, meaning they will stretch. If we wanted to set the ''scaleX ''property to reflect a specific width that we want, we can divide the width we want by the width of the current size of the object, like so: _mySprite.scaleX = 60 / _mySprite.width; Assuming that the width of ''_mySprite ''is 20, when we divide 60 by 20, the result is 3. This will multiply the total width of the Sprite by 3 and result in 60, which is the width we want. These are some basic transformation examples, but what about rotation? We can also do that! The ''rotation ''property of the DisplayObject class allows us to do that, which takes a Float of given degrees. The good thing about this is that we can be very specific about what rotation we want. In addition, if we add any more than the value 360 to the DisplayObject, it will subtract accordingly to give us the rotation result, which means we don't have to do the maths ourselves. _mySprite.rotation = 180; The above code will rotate our Sprite 180 degrees clockwise. If we make the value minus (below zero), the rotation will operate counterclockwise. We can also set the alpha of the entire DisplayObject, which will change the opacity of itself and all containing objects (if any). An alpha value of 0 means the DisplayObject is entirely transparent, and cannot be seen. But it is still technically visible and can still be interacted with. _mySprite.alpha = 0.3 The above code gives us 30% opacity (or 70% transparency). If we do not want a DisplayObject to be visible, adjusting the ''visible ''property may be more appropriate. _mySprite.visible = false; Setting the visibility of a DisplayObject to false means that it can no longer be interacted with. Transformation should not be confused with the ''transform ''property of the DisplayObject, which gives us more options to transform an object, including skew, hues and other such details. These specific transformations can be too much for one tutorial, so I will leave that out for a different tutorial, but it is worth noting that such transformations depend on a snapshot of an existing DisplayObject. Working with the Graphics class The Graphics class, or ''graphics ''property of the Sprite, gives us the ability to draw shapes into a given area within our working sprite. The ''graphics ''property is exclusive only to the Sprite class, and is the only object in the OpenFL framework that can do such a thing. The Graphics class can do a lot of things, including gradients, line styles, colours, shapes and even transformations. You could theoretically create an entire game using purely the Graphics class, which is not impossible, but difficult. The Graphics class is very complex, so I will not go into too much detail, but I will introduce some basic ways in which you can use it. The Graphics is particularly useful when you want to draw some basic shapes, so let's draw a rectangle. To do this, we will take advantage of the ''drawRect() ''function: _mySprite.graphics.beginFill(0xFF0000, 1); _mySprite.graphics.drawRect(0, 0, 150, 150); Let's split the above example into two individual lines and explain what each one does. The first line: _mySprite.graphics.beginFill(0xFF0000, 1); Tells the Graphics class that all drawing routines between this beginFill() and the next beginFill() call will operate based on the style we give it. In this style, we are giving the first parameter - which is a colour - the hexadecimal value ''FF0000 which is the equivalent of red. Hexadecimal values are values between 0 and F, using numbers from 0-9, and then A-F after. These translate into binary eventually, and the letter F represents 11111111 in binary. The use of FF0000 ''converts to: 111111111111111100000000000000000000000000000000 That is 16 1's and 32 0's in that order. That represents one colour pixel, which is 24-bit. Each bit in a binary value is represented as one binary value. Therefore: 0000 Is worth 4-bits. This isn't a binary lesson, but it's worth noting that each hexadecimal value is worth 4-bits, and since we have 6 in total, that becomes 24-bits. It is possible to include an additional pair of hexadecimal values representing the alpha value of a colour, which makes it 32-bit and taking up more memory, but this is done with the second parameter of the ''beginFill ''function. The second parameter, which is 1 in the above example, denotes that the colour is fully opaque. After that, we move onto our next line: _mySprite.graphics.drawRect(0, 0, 150, 150); This is drawing a rectangle from the location (0, 0) and with a size of 150x150. '''Note' If you set the x and y location of the drawn rectangle to anything other than (0, 0), then the total size of the Sprite will modify to reflect these changes. Therefore a change from (0, 0) to (50, 50) in the above example will result in the Sprite becoming 200x200. It is also worth noting that all graphics you draw is relative to the location of the container Sprite, so when you move the Sprite, all the graphics within it will also move. This also includes scaling and transformations to the Sprite. That is just a small example of what the Graphics can do, but it can do so much more. Scrolling and Clipping Sometimes you want to be able to contain lots of UI elements within any given sprite, like - for example - an options menu within your game. There is an easy fix to that, and that is to use the scrollRect property of the DisplayObject. The DisplayObject will clip to match the rectangle you give this property. _mySprite.scrollRect = new Rectangle(0, 0, 400, 400); The above example gives us the ability to clip the sprite to its default registration (0, 0) but limits its size to 400x400. But we won't be able to scroll it until we manually listen on an event to make this happen. _mySprite.addEventListener(MouseEvent.MOUSE_WHEEL, function(e:MouseEvent) { if (e.delta > 0) _mySprite.scrollRect.y++; else if (e.delta < 0) _mySprite.scrollRect.y--; }); The code above listens on the MOUSE_WHEEL event of a sprite, and when the ''delta ''property changes on the event - the property associated with the value given on the mouse wheel scrolling - the x and y position of the Sprite's scrollRect changes accordingly. The reason we change the x and y properties is because the contents of the Sprite will scroll accordingly. If the content is scrolled to as far as the total height of the Sprite, the content will stop moving. Using scale9Grid The ''scale9Grid ''property of a DisplayObject allows us to define the stretching bounds of the DisplayObject, while leaving the outer parts of it intact. The way we do this is by setting the ''scale9Grid ''with a Rectangle. _mySprite.scale9Grid = new Rectange(15, 15, 75, 20); The location (15, 15) defines the top-left corner of the stretching bounds of a Sprite, while the size refers to the total width and height of the stretching bounds. Everything around the stretching bounds will be left intact when we modify the ''scaleX ''and ''scaleY ''properties. Using the above example, below is an image representing a button with the size matching that of above. The scale9Grid when associated with the above image will be represented by red outlines as shown below (not literally in-game). Everything within the red outline will be stretched when the image is resized, while everything outside of the red outline will remain intact. References